#include <mc/block.h>
#include <mc/blockImp.h>
#include <opengl/texture.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <map>
#include <iostream>

namespace mc
{

// 方块着色器程序
opengl::Shader Block::shader;

// 方块顶点数据
float Block::vertices[] = {
    // Back face
     0.0f,  0.0f,  0.0f,  0.0f, 0.0f, // Bottom-left
     1.0f,  1.0f,  0.0f,  1.0f, 1.0f, // top-right
     1.0f,  0.0f,  0.0f,  1.0f, 0.0f, // bottom-right         
     1.0f,  1.0f,  0.0f,  1.0f, 1.0f, // top-right
     0.0f,  0.0f,  0.0f,  0.0f, 0.0f, // bottom-left
     0.0f,  1.0f,  0.0f,  0.0f, 1.0f, // top-left
    // Front face
     0.0f,  0.0f,  1.0f,  0.0f, 0.0f, // bottom-left
     1.0f,  0.0f,  1.0f,  1.0f, 0.0f, // bottom-right
     1.0f,  1.0f,  1.0f,  1.0f, 1.0f, // top-right
     1.0f,  1.0f,  1.0f,  1.0f, 1.0f, // top-right
     0.0f,  1.0f,  1.0f,  0.0f, 1.0f, // top-left
     0.0f,  0.0f,  1.0f,  0.0f, 0.0f, // bottom-left
    // Left face
     0.0f,  1.0f,  1.0f,  1.0f, 1.0f, // top-right
     0.0f,  1.0f,  0.0f,  0.0f, 1.0f, // top-left
     0.0f,  0.0f,  0.0f,  0.0f, 0.0f, // bottom-left
     0.0f,  0.0f,  0.0f,  0.0f, 0.0f, // bottom-left
     0.0f,  0.0f,  1.0f,  1.0f, 0.0f, // bottom-right
     0.0f,  1.0f,  1.0f,  1.0f, 1.0f, // top-right
    // Right face 
     1.0f,  1.0f,  1.0f,  0.0f, 1.0f, // top-left
     1.0f,  0.0f,  0.0f,  1.0f, 0.0f, // bottom-right
     1.0f,  1.0f,  0.0f,  1.0f, 1.0f, // top-right         
     1.0f,  0.0f,  0.0f,  1.0f, 0.0f, // bottom-right
     1.0f,  1.0f,  1.0f,  0.0f, 1.0f, // top-left
     1.0f,  0.0f,  1.0f,  0.0f, 0.0f, // bottom-left   
    // Bottom face
     0.0f,  0.0f,  0.0f,  0.0f, 1.0f, // top-right
     1.0f,  0.0f,  0.0f,  1.0f, 1.0f, // top-left
     1.0f,  0.0f,  1.0f,  1.0f, 0.0f, // bottom-left
     1.0f,  0.0f,  1.0f,  1.0f, 0.0f, // bottom-left
     0.0f,  0.0f,  1.0f,  0.0f, 0.0f, // bottom-right
     0.0f,  0.0f,  0.0f,  0.0f, 1.0f, // top-right
    // Top face
     0.0f,  1.0f,  0.0f,  0.0f, 1.0f, // top-left
     1.0f,  1.0f,  1.0f,  1.0f, 0.0f, // bottom-right
     1.0f,  1.0f,  0.0f,  1.0f, 1.0f, // top-right     
     1.0f,  1.0f,  1.0f,  1.0f, 0.0f, // bottom-right
     0.0f,  1.0f,  0.0f,  0.0f, 1.0f, // top-left
     0.0f,  1.0f,  1.0f,  0.0f, 0.0f  // bottom-left     
};

#define TOTAL_ELEMENT_CNT (sizeof(Block::vertices) / sizeof(float))
#define FACE_ELEMENT_CNT (TOTAL_ELEMENT_CNT / 6)

// 构造函数
Block::Block(unsigned int textureCnt): 
    _instanceVBO(), _instanceMatrices{} {}

Block::~Block() {}

OneTextureBlock::~OneTextureBlock()
{
    this->_vao.free();
    this->_vbo.free();
    this->_instanceVBO.free();
}

TwoTexturesBlock::~TwoTexturesBlock()
{
    this->_vaos[0].free();
    this->_vbos[0].free();
    this->_vaos[1].free();
    this->_vbos[1].free();
    this->_instanceVBO.free();
}

ThreeTexturesBlock::~ThreeTexturesBlock()
{
    this->_vaos[0].free();
    this->_vbos[0].free();
    this->_vaos[1].free();
    this->_vbos[1].free();
    this->_vaos[2].free();
    this->_vbos[2].free();
    this->_instanceVBO.free();
}

FourTexturesBlock::~FourTexturesBlock()
{
    this->_vaos[0].free();
    this->_vbos[0].free();
    this->_vaos[1].free();
    this->_vbos[1].free();
    this->_vaos[2].free();
    this->_vbos[2].free();
    this->_vaos[3].free();
    this->_vbos[3].free();
    this->_instanceVBO.free();   
}

// 放置新方块
void Block::place(const glm::vec3& position, bool upadate)
{
    // 计算 model 矩阵
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, position);

    // 添加实例化数组
    this->_instanceMatrices.push_back(model);

    // 更新实例化数组数据
    if (upadate) 
        this->updatePositions();
}

// 移除方块
void Block::remove(const glm::vec3& position, bool update)
{
    // 计算 model 矩阵
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, position);

    for (auto iter = this->_instanceMatrices.cbegin(); iter != this->_instanceMatrices.cend(); ++iter)
    {
        if ((*iter) == model)
        {
            this->_instanceMatrices.erase(iter);
            break;
        }
    }

    if (update)
        this->updatePositions();
}

// 准备循环
void Block::prepareRender(const glm::mat4& view)
{
    Block::shader.bind();
    Block::shader.setMat4("view", view);
    Block::shader.setVec4("backColor", glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
}

// 加载着色器程序
void Block::createShader(const glm::mat4& projection)
{
    // 方块着色器程序
    Block::shader = opengl::Shader("./resources/shaders/block.vert", "./resources/shaders/block.frag");
    Block::shader.bind();
    Block::shader.setMat4("projection", projection);
    Block::shader.unbind();
}

void OneTextureBlock::draw()
{
    BlockImp::drawInstantiation(this->_vao, this->textures()[0], 36, this->_instanceMatrices.size());
}

void TwoTexturesBlock::draw()
{
    BlockImp::drawInstantiation(this->_vaos[0], this->textures()[0], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[1], this->textures()[1], 30, this->_instanceMatrices.size());
}

void ThreeTexturesBlock::draw()
{
    BlockImp::drawInstantiation(this->_vaos[0], this->textures()[0], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[1], this->textures()[1], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[2], this->textures()[2], 24, this->_instanceMatrices.size());
}

void FourTexturesBlock::draw()
{
    BlockImp::drawInstantiation(this->_vaos[0], this->textures()[0], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[1], this->textures()[1], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[2], this->textures()[2], 6, this->_instanceMatrices.size());
    BlockImp::drawInstantiation(this->_vaos[3], this->textures()[3], 18, this->_instanceMatrices.size());
}

void OneTextureBlock::updatePositions()
{
    BlockImp::updatePositions(this->_vao, this->_instanceVBO, this->_instanceMatrices);
}

void TwoTexturesBlock::updatePositions()
{
    BlockImp::updatePositions(this->_vaos[0], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[1], this->_instanceVBO, this->_instanceMatrices);
}

void ThreeTexturesBlock::updatePositions()
{
    BlockImp::updatePositions(this->_vaos[0], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[1], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[2], this->_instanceVBO, this->_instanceMatrices);
}

void FourTexturesBlock::updatePositions()
{
    BlockImp::updatePositions(this->_vaos[0], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[1], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[2], this->_instanceVBO, this->_instanceMatrices);
    BlockImp::updatePositions(this->_vaos[3], this->_instanceVBO, this->_instanceMatrices);
}

// 一种纹理构造函数
OneTextureBlock::OneTextureBlock() : Block(1), _vao{}, _vbo{}
{
    BlockImp::initVertexArray(this->_vao, this->_vbo, sizeof(Block::vertices), Block::vertices);
}

// 两种纹理构造函数
TwoTexturesBlock::TwoTexturesBlock() : Block(2), _vaos{}, _vbos{}
{
    BlockImp::initVertexArray(this->_vaos[0], this->_vbos[0], 2 * sizeof(Block::vertices) / 6, &Block::vertices[4 * FACE_ELEMENT_CNT]);
    BlockImp::initVertexArray(this->_vaos[1], this->_vbos[1], 4 * (sizeof(Block::vertices) / 6), Block::vertices);
}

// 三种纹理的构造
ThreeTexturesBlock::ThreeTexturesBlock() : Block(3), _vaos{}, _vbos{}
{
    BlockImp::initVertexArray(this->_vaos[0], this->_vbos[0], sizeof(Block::vertices) / 6, &Block::vertices[5 * FACE_ELEMENT_CNT]);
    BlockImp::initVertexArray(this->_vaos[1], this->_vbos[1], sizeof(Block::vertices) / 6, &Block::vertices[4 * FACE_ELEMENT_CNT]);
    BlockImp::initVertexArray(this->_vaos[2], this->_vbos[2], 4 * (sizeof(Block::vertices) / 6), Block::vertices);
}

// 四种纹理的方块枚举
FourTexturesBlock::FourTexturesBlock() : Block(4), _vaos{}, _vbos{}
{
    BlockImp::initVertexArray(this->_vaos[0], this->_vbos[0], sizeof(Block::vertices) / 6, &Block::vertices[5 * FACE_ELEMENT_CNT]);
    BlockImp::initVertexArray(this->_vaos[1], this->_vbos[1], sizeof(Block::vertices) / 6, &Block::vertices[4 * FACE_ELEMENT_CNT]);
    BlockImp::initVertexArray(this->_vaos[2], this->_vbos[2], (sizeof(Block::vertices) / 6), Block::vertices);
    BlockImp::initVertexArray(this->_vaos[3], this->_vbos[3], 3 * (sizeof(Block::vertices) / 6), &Block::vertices[FACE_ELEMENT_CNT]);
}

}